# -*- coding: utf-8 -*-
"""
Created on Mon May 31 09:44:51 2021

@author: huangyue
"""
import numpy as np
import pandas as pd
import datetime
from scipy import interpolate
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号
d1 = datetime.timedelta(days=1)





# %% 策略基本函数
def get_enddate(anadata = None):
    '''
    大致获取最新一期的交易日
    '''
    if anadata is None:        
        enddate = (datetime.datetime.now() - d1*17.5/24)
        enddate = enddate+d1*(4 - (enddate.weekday() if enddate.weekday()>=5 else 4)) # 如果是周六周日则调整为周五
        enddate = enddate.date()
    else:
        enddate = anadata['date'].max().date()
    enddate = datetime.datetime(enddate.year, enddate.month,enddate.day)
    return enddate

"""   
收益分析
""" 
def profit_analysis(pnl,begdate = None,enddate = None,
                    plot_on = False, rf_rate=0.02,
                    get_outcome = True):
    '''
    input: date,portfolio
    '''
    if begdate is not None:
        pnl = pnl[pnl['date']>=pd.to_datetime(begdate)]
    if enddate is not None:
        pnl = pnl[pnl['date']<=pd.to_datetime(enddate)]       
    if 'rf rate' not in pnl.columns.values:
        pnl['rf rate'] = rf_rate 
    if 'portfolio_ret' not in pnl.columns.values:
        pnl['portfolio_ret'] = pnl['portfolio'].pct_change()
        pnl['portfolio_ret'].fillna(0,inplace=True)
    
    pnl['portfolio'] = pnl['portfolio']/pnl['portfolio'].iloc[0]

    rf_rate = pnl['rf rate'].mean()
    len_df = pnl.shape[0]

    pnl['max_pnl'] = pnl['portfolio'].cummax()
    pnl['drawdown'] = (pnl['portfolio'] - pnl['max_pnl']) / pnl['max_pnl']
    
    # 绘制收益情况
    if plot_on:
        fig,ax1 = plt.subplots()
        ax2 = ax1.twinx()
        ax1.bar(pnl['date'],pnl['drawdown']) 
        ax2.plot(pnl['date'],pnl['portfolio'])        
        ax1.set_ylabel('drawdown')
        ax2.set_ylabel('PNL')         
        # ax2.set_ylim(0.95,2)
        # ax1.set_ylim(-0.1,0)
    
    # 年化收益
    annual_ret = pnl['portfolio'].iloc[-1]**(250/len_df)-1
    # 年化波动
    annual_var = pnl['portfolio_ret'].var()*(len_df - 1)/len_df * 250
    annual_std = np.sqrt(annual_var)    
    # 最大回撤
    max_drawdown = pnl['drawdown'].min()
    # 夏普比率
    sharpeRatio = (annual_ret - rf_rate) / annual_std
    # Calmar比率
    calmarRatio = -annual_ret / max_drawdown
    # 胜率
    pnl['week'] = pnl['date'].apply(lambda x : pd.to_datetime(x).strftime('%W'))
    pnl['year'] = pnl['date'].apply(lambda x : x.year)
    pnl_w = pnl.groupby(['year','week']).last()
    pnl_w['portfolio_ret'] = pnl_w['portfolio'].pct_change()
    winningRatio = (pnl_w['portfolio_ret']>0).astype('int').sum()\
                    /pnl_w['portfolio_ret'].dropna().count()
    print('*'*10 + ' ' + pnl['date'].min().strftime('%Y/%m/%d')+\
          ' - '+pnl['date'].max().strftime('%Y/%m/%d') + ' ' + '*'*10)
    print('total return: '+str(round(pnl['portfolio'].iloc[-1]*100-100,3)) + '%')
    print('annual return: '+str(round(annual_ret*100,3)) + '%')
    # print('annual variance: '+str(round(annual_var*100,3)) + '%')
    print('annual std: '+str(round(annual_std*100,3)) + '%')
    print('max drawdown: '+str(round(max_drawdown*100,3)) + '%')
    print('sharpe ratio: '+str(round(sharpeRatio,3)))
    print('Calmar ratio: '+str(round(calmarRatio,3)))
    print('Winning ratio: '+str(round(winningRatio*100,3))+'%')
    
    outcome = {'total return':pnl['portfolio'].iloc[-1]-1,
               'annual return':annual_ret,
               'annual std':annual_std,
               'max drawdown':max_drawdown,
               'sharpe ratio':sharpeRatio,
               'Calmar ratio':calmarRatio,
               'Winning ratio':winningRatio}
    if get_outcome:
        return outcome

# %% 数据表操作基本函数
'''宽表转长表'''
def reverse_pivot(df):
    '''
    传入一个宽表：
    
    index  col1     col2     col3  
    1      value11  value12  value13
    2      value21  value22  value23
    3      value31  value32  value33
    
    返回一张长表:
    multiindex  value    
    1  col1    value11  
    1  col2    value12  
    1  col3    value13  
    2  col1    value21  
    2  col2    value22  
    2  col3    value23  
    3  col1    value31  
    3  col2    value32  
    3  col3    value33  
    '''
    df_new = pd.DataFrame(columns = ['value'],
                     data = np.concatenate(df.values),
                     index = pd.MultiIndex.from_product([df.index,df.columns]))
    return df_new


'''表连接'''
def my_left_join(df1,df2,on='date'):
    colname = list(df2.columns)
    colname.remove(on)
    df1 = df1.reset_index()
    df2 = df2.reset_index()
    df = pd.merge(df1,df2,on=on)
    
    df1.loc[df['index_x'],colname] = df2.loc[df['index_y'],colname].values

    return df1.drop(columns='index')

'''# %% 插值法'''
def get_intradata(timeSeries,data):
    stry = list(data.columns)
    stry.remove('date')
    stry = stry[0]
    new_data = my_left_join(timeSeries, data, on='date')

    x = pd.merge(timeSeries.reset_index(),data, on='date')['index'].values
    y = new_data.loc[x,stry].values
    
    f = interpolate.interp1d(x,y,kind='cubic')
    begindex = x[0]
    endindex = x[-1]
    ynew = f(new_data.index[begindex:endindex+1])
    new_data.loc[new_data.index[begindex:endindex+1],stry+'_n'] = ynew
    return new_data


# %% 转债基本函数

def get_premiumdata(anadata1, enddate, n=1):
    '''
    计算可转债在不同平下的过去n年的分位数
    '''
    plotdata = anadata1[(anadata1['date'] >= enddate - d1 * 365 * n) &  (anadata1['date']<=enddate)]
    
    premiumdata = pd.DataFrame(columns=['ParPrice','5','20','50','80','95'])
    
    tmpindex = -1
    
    for tmppar in range(40,200,1):
        minpar = tmppar
        maxpar = tmppar+5
        
        tmpplotdata = plotdata[(plotdata['ParPrice']>=minpar) & (plotdata['ParPrice']<=maxpar)]
        tmpindex = tmpindex + 1
        
        premiumdata.loc[tmpindex,'ParPrice'] = tmppar
        premiumdata.loc[tmpindex,'5'] = tmpplotdata['CBPrice'].quantile(0.05)
        premiumdata.loc[tmpindex,'20'] = tmpplotdata['CBPrice'].quantile(0.2)
        premiumdata.loc[tmpindex,'50'] = tmpplotdata['CBPrice'].quantile(0.5)
        premiumdata.loc[tmpindex,'80'] = tmpplotdata['CBPrice'].quantile(0.8)
        premiumdata.loc[tmpindex,'95'] = tmpplotdata['CBPrice'].quantile(0.95)

    return premiumdata








# %% 对转债做反比例函数拟合

def get_clean_data_for_model(basicinfo,amt,quote):
    '''
    转债数据处理
    
    get clean data for fitness.

    Parameters
    ----------
    basicinfo : pd.DataFrame
        columns=['InnerCode','CBCode','CBAbbr','Exchange','SCode','SAbbr','ListedDate','IssueSize']
    amt : pd.DataFrame
        columns=['InnerCode','EndDay','EvenType','IssueAmt','RemainAmt']
    quote : pd.DataFrame
        columns=['InnerCode','TradeDay','PremiumRate','ParPrice','TurnoverRate','ClosePrice']

    Returns
    -------
    data : pd.DataFrame
        columns = ['TradeDay','CBCode','CBAbbr','Exchange','SCode','SAbbr','PremiumRate','ParPrice','TurnoverRate','ClosePrice']

    '''
    # 增加债券余额信息
    listinfo = basicinfo[['InnerCode','ListedDate','IssueSize']].dropna()\
        .rename(columns={'ListedDate':'EndDay','IssueSize':'IssueAmt'})
    listinfo['EvenType'] = 0
    listinfo['IssueAmt'] = listinfo['IssueAmt']*1000000
    listinfo['RemainAmt'] = listinfo['IssueAmt']
    amt1 = pd.concat([amt,listinfo])
    
    # 删去换手率超过40%的数据
    quote = quote[quote['TurnoverRate']<=40]  
    # 删去转换价值较为异常的数据
    quote = quote[(quote['ParPrice']<=130) & (quote['ParPrice']>=70)]
    # 删去转股溢价率较为异常的数据
    quote = quote[(quote['PremiumRate']<=300) & (quote['PremiumRate']>=-10)]
    
    # 合并三个表
    data = pd.merge(quote,basicinfo,on='InnerCode')   # 非CB部分被筛选掉
    data = pd.merge_asof(data.sort_values(by='TradeDay'), amt1.sort_values(by='EndDay'), left_on='TradeDay', right_on='EndDay',by='InnerCode')
    data['RemainRatio'] = data['RemainAmt']/data['IssueAmt']
    
    data = data[data['RemainRatio']>0.05]  # 删除余额占比不足5%的债券
    data = data[data['RemainAmt']>200000000]  # 删除余额不足2亿的债券
    
    data['Exchange'] = data['Exchange'].replace({83:'SH',90:'SZ'})    
    data = data[['TradeDay','CBCode','CBAbbr','Exchange','SCode','SAbbr',
                 'PremiumRate','ParPrice','TurnoverRate','ClosePrice']]
    data['PremiumRate'] = data['PremiumRate']/100
    
    return data


def get_inverse_para(data):
    # 最小二乘法求解反比例函数
    data['y/x'] = data['y']/data['x']
    data['1/x'] = 1/data['x']
    data['1/x2'] = 1/(data['x']**2)
    tmpsum = data.sum()
    n = data.shape[0]
    a = (tmpsum['1/x'] * tmpsum['y/x'] - tmpsum['1/x2'] * tmpsum['y'])/\
        (tmpsum['1/x'] * tmpsum['1/x'] - tmpsum['1/x2'] * n)
    b = (tmpsum['1/x'] * tmpsum['y'] - n * tmpsum['y/x'])/\
        (tmpsum['1/x'] * tmpsum['1/x'] - tmpsum['1/x2'] * n)
    return a,b


'''
对转债做反比例函数拟合
'''
def fit_singleday_para(variable_data):
    '''

    y = a+b/x
    
    Parameters
    ----------
    variable_data : dataframe
        columns:x,y for fitness

    Returns
    -------
    a,b: paras of fitness.

    '''
    # （2）数据处理
    variable_data['y0'] = variable_data['y']
    variable_data = variable_data[variable_data['x']<=250].reset_index(drop=True)
    variable_data = variable_data[variable_data['y']<=3].reset_index(drop=True)
    
    # （3）初始回归
    a,b=get_inverse_para(variable_data[['x','y']])
    variable_data['y_pre'] = a + b/variable_data['x']
    R2=((variable_data['y_pre']-variable_data['y'].mean())**2).sum()/((variable_data['y']-variable_data['y'].mean())**2).sum()
    # check_fitness(variable_data,R2)
    # （4）修改异常值，进行回归迭代
    R2_0 = 0.1
    # 拟合优度高于90%或者提升空间很低的时候不再迭代
    while ((R2/R2_0-1)>0.01) & (R2<=0.9):
        # 残差高于90%分位数的点定义为异常值
        tmpindex = variable_data[abs(variable_data['y']-variable_data['y_pre'])>abs(variable_data['y']-variable_data['y_pre']).quantile(0.9)].index
        # 修改异常值
        variable_data.loc[tmpindex,'y'] = (variable_data.loc[tmpindex,'y'] + variable_data.loc[tmpindex,'y_pre'])/2
        # 回归
        a,b = get_inverse_para(variable_data[['x','y']])
        # 查看回归效果
        variable_data['y_pre'] = a + b/variable_data['x']
        R2_0 = R2
        R2 = ((variable_data['y_pre']-variable_data['y'].mean())**2).sum()/((variable_data['y']-variable_data['y'].mean())**2).sum()
        # check_fitness(variable_data,R2)
    return pd.DataFrame([{'a':a,'b':b}])


def check_fitness(data,R2):
    # 检查拟合优度
    plt.figure()
    plt.scatter(data['x'],data['y'])
    plt.scatter(data['x'],data['y_pre'])
    plt.title(R2 )
    
